package com.hsalf.smilerating;

import com.hsalf.smileyrating.ArgbEvaluator;
import com.hsalf.smileyrating.DisplayAttributes;
import com.hsalf.smileyrating.FloatEvaluator;
import com.hsalf.smileyrating.ResourceTable;
import com.hsalf.smileyrating.ValueAnimator;
import com.hsalf.smileyrating.smileys.base.RectF;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbPalette;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class SmileRating extends BaseRating
    implements Component.DrawTask, Component.TouchEventListener, Component.LayoutRefreshedListener,
    Component.EstimateSizeListener {

    private int mPlaceHolderSmileColor = Color.WHITE.getValue();

    private int mAngryColor = RgbPalette.parse("#f29a68");

    private int mNormalColor = RgbPalette.parse("#f2dd68");

    private int mDrawingColor = RgbPalette.parse("#353431");

    private int mTextSelectedColor = Color.BLACK.getValue();

    private int mTextNonSelectedColor = RgbPalette.parse("#AEB3B5");

    private int mPlaceholderBackgroundColor = RgbPalette.parse("#e6e8ed");

    private String[] mNames = getResourceManager().getElement(ResourceTable.Strarray_names).getStringArray();

    private Face[] mFaces = new Face[SMILES_LIST.length];

    private Map<Integer, Point> mTouchPoints = new HashMap<>();

    private float mSmileGap;

    private boolean mShowLine = true;

    private float mMainSmileyTransformaFraction = 1;

    private Paint mPathPaint = new Paint();

    private Paint mBackgroundPaint = new Paint();

    private Paint mPointPaint1 = new Paint();

    private Paint mPointPaint2 = new Paint();

    private Point mFaceCenter = new Point();

    private Path mSmilePath = new Path();

    private Paint mPlaceHolderFacePaint = new Paint();

    private Paint mPlaceholderLinePaint = new Paint();

    private Paint mPlaceHolderCirclePaint = new Paint();

    private FloatEvaluator mFloatEvaluator = new FloatEvaluator();

    private ValueAnimator mValueAnimator = ValueAnimator.getInstance();

    private ArgbEvaluator mColorEvaluator = new ArgbEvaluator();

    private Matrix mScaleMatrix = new Matrix();

    private ValueAnimator.CurveType mInterpolator;

    private float divisions;

    private ClickAnalyser mClickAnalyser;

    private RectF mScaleRect = new RectF();

    private RectF mTouchBounds = new RectF();

    private Path mDummyDrawPah = new Path();

    private Paint mTextPaint = new Paint();

    @Smiley
    private int mSelectedSmile = NONE;

    @Smiley
    private int mPreviousSmile = -1;

    @Smiley
    private int mNearestSmile = NONE;

    @Smiley
    private int mPendingActionSmile = NONE;

    private Smileys mSmileys;

    private float mWidth;

    private float mHeight;

    private float mCenterY;

    private float mFromRange;

    private float mToRange;

    private float mPrevX;

    private boolean mFaceClickEngaged = false;

    private float mPlaceHolderScale = 1f;

    private boolean mSmileyNotSelectedPreviously = true;

    private boolean mIndicator = false;

    private OnRatingSelectedListener mOnRatingSelectedListener = null;

    private OnSmileySelectionListener mOnSmileySelectionListener = null;

    public SmileRating(Context context) throws NotExistException, WrongTypeException, IOException {
        super(context);
    }

    public SmileRating(Context context, AttrSet attrSet) throws NotExistException, WrongTypeException, IOException {
        super(context, attrSet);
        parseAttrs(attrSet);
        init();

    }

    public SmileRating(Context context, AttrSet attrSet, String styleName)
        throws NotExistException, WrongTypeException, IOException {
        super(context, attrSet, styleName);
        parseAttrs(attrSet);
        init();

    }

    public SmileRating(Context context, AttrSet attrSet, int resId)
        throws NotExistException, WrongTypeException, IOException {
        super(context, attrSet, resId);
        parseAttrs(attrSet);
        init();

    }

    private void parseAttrs(AttrSet attrSet) {
        if (attrSet != null) {
            mAngryColor = attrSet.getAttr("SmileRating_angryColor").isPresent() ? attrSet.getAttr(
                "SmileRating_angryColor").get().getColorValue().getValue() : mAngryColor;

            System.out.println("SmileRating_angryColor :" + mAngryColor);

            mNormalColor = attrSet.getAttr("SmileRating_normalColor").isPresent() ? attrSet.getAttr(
                "SmileRating_normalColor").get().getColorValue().getValue() : mNormalColor;

            mDrawingColor = attrSet.getAttr("SmileRating_drawingColor").isPresent() ? attrSet.getAttr(
                "SmileRating_drawingColor").get().getColorValue().getValue() : mDrawingColor;

            mPlaceHolderSmileColor = attrSet.getAttr("SmileRating_placeholderSmileColor").isPresent() ? attrSet.getAttr(
                "SmileRating_placeholderSmileColor").get().getColorValue().getValue() : mPlaceHolderSmileColor;

            mPlaceholderBackgroundColor = attrSet.getAttr("SmileRating_placeholderBackgroundColor").isPresent()
                ? attrSet.getAttr("SmileRating_placeholderBackgroundColor").get().getColorValue().getValue()
                : mPlaceholderBackgroundColor;

            mTextSelectedColor = attrSet.getAttr("SmileRating_textSelectedColor").isPresent() ? attrSet.getAttr(
                "SmileRating_textSelectedColor").get().getColorValue().getValue() : mTextSelectedColor;

            mTextNonSelectedColor = attrSet.getAttr("SmileRating_textNonSelectedColor").isPresent() ? attrSet.getAttr(
                "SmileRating_textNonSelectedColor").get().getColorValue().getValue() : mTextNonSelectedColor;

            mShowLine = attrSet.getAttr("SmileRating_showLine").isPresent() ? attrSet.getAttr("SmileRating_showLine")
                .get()
                .getBoolValue() : true;

            mIndicator = attrSet.getAttr("SmileRating_isIndicator").isPresent() ? attrSet.getAttr(
                "SmileRating_IsIndicator").get().getBoolValue() : false;

        }

    }

    private void init() {
        mClickAnalyser = ClickAnalyser.newInstance(DisplayAttributes.getDensity(getContext()));
        mTextPaint.setAntiAlias(true);
        mTextPaint.setFont(Font.DEFAULT_BOLD);

        mPathPaint.setAntiAlias(true);
        mPathPaint.setStrokeWidth(3);
        mPathPaint.setColor(new Color(mDrawingColor));
        mPathPaint.setStyle(Paint.Style.FILL_STYLE);

        mPointPaint1.setAntiAlias(true);
        mPointPaint1.setColor(Color.RED);
        mPointPaint1.setStyle(Paint.Style.FILL_STYLE);

        mPointPaint2.setAntiAlias(true);
        mPointPaint2.setColor(Color.BLUE);
        mPointPaint2.setStyle(Paint.Style.STROKE_STYLE);

        mBackgroundPaint.setAntiAlias(true);
        mBackgroundPaint.setStyle(Paint.Style.FILL_STYLE);

        mPlaceHolderFacePaint.setAntiAlias(true);
        mPlaceHolderFacePaint.setColor(new Color(mPlaceHolderSmileColor));
        mPlaceHolderFacePaint.setStyle(Paint.Style.FILL_STYLE);

        mPlaceHolderCirclePaint.setAntiAlias(true);
        mPlaceHolderCirclePaint.setColor(new Color(mPlaceholderBackgroundColor));
        mPlaceHolderCirclePaint.setStyle(Paint.Style.FILL_STYLE);

        mPlaceholderLinePaint.setAntiAlias(true);
        mPlaceholderLinePaint.setColor(new Color(mPlaceholderBackgroundColor));
        mPlaceholderLinePaint.setStyle(Paint.Style.STROKE_STYLE);

        mValueAnimator.setDuration(350);
        mValueAnimator.setStateChangedListener(mAnimatorListener);
        mValueAnimator.setValueUpdateListener(mAnimatorUpdateListener);
        mValueAnimator.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);

        setEstimateSizeListener(this::onEstimateSize);
        setLayoutRefreshedListener(this::onRefreshed);
        addDrawTask(this::onDraw);
        setTouchEventListener(this::onTouchEvent);

    }

    AnimatorValue.ValueUpdateListener mAnimatorUpdateListener = new AnimatorValue.ValueUpdateListener() {
        @Override
        public void onUpdate(AnimatorValue animatorValue, float v) {
            if (mSmileyNotSelectedPreviously) {
                if (NONE == mSelectedSmile) {
                    mMainSmileyTransformaFraction = 1f - mMainSmileyTransformaFraction;
                }
                invalidate();
            } else {
                float anim = v;
                moveSmile(anim);
            }
        }
    };

    Animator.StateChangedListener mAnimatorListener = new Animator.StateChangedListener() {
        @Override
        public void onStart(Animator animator) {
            if (NONE != mSelectedSmile) {
                moveSmile(mTouchPoints.get(mSelectedSmile).x);
            }

        }

        @Override
        public void onStop(Animator animator) {

        }

        @Override
        public void onCancel(Animator animator) {

        }

        @Override
        public void onEnd(Animator animator) {
            notifyListener();
        }

        @Override
        public void onPause(Animator animator) {

        }

        @Override
        public void onResume(Animator animator) {

        }
    };

    private void notifyListener() {
        boolean reselected = mPreviousSmile == getSelectedSmile();
        mPreviousSmile = mSelectedSmile;
        mPendingActionSmile = mSelectedSmile;
        if (mOnSmileySelectionListener != null) {
            mOnSmileySelectionListener.onSmileySelected(mSelectedSmile, reselected);
        }
        if (mOnRatingSelectedListener != null) {
            mOnRatingSelectedListener.onRatingSelected(getRating(), reselected);
        }
    }

    public void setOnSmileySelectionListener(OnSmileySelectionListener l) {
        mOnSmileySelectionListener = l;
    }

    public void setOnRatingSelectedListener(OnRatingSelectedListener l) {
        mOnRatingSelectedListener = l;
    }

    @Override
    public void onRefreshed(Component component) {
        onMeasure();
    }

    private void onMeasure() {
        mWidth = getWidth();
        mHeight = mWidth / (5.3f * 1.3f);
        mCenterY = mHeight / 2f;
        mFaceCenter.y = mCenterY;
        divisions = (mHeight / 32f);
        mTextPaint.setTextSize((int) (mHeight / 4.5f));
        mSmileys = Smileys.newInstance(Math.round(mWidth), Math.round(mHeight));
        setComponentSize(Math.round(mWidth), (int) Math.round(mHeight + (mHeight * 0.48)));
        createTouchPoints();
        mPlaceholderLinePaint.setStrokeWidth(mHeight * 0.05f);
        setSelectedSmile(mPendingActionSmile, mTouchPoints.get(mPendingActionSmile), false, false);
    }

    private void setSelectedSmile(@Smiley int smile, Point point, boolean check, boolean animate) {
        if (mSelectedSmile == smile && check) {
            return;
        }
        if (mSelectedSmile == NONE) {
            mSmileyNotSelectedPreviously = true;
        } else if (smile == NONE) {
            mSmileyNotSelectedPreviously = true;
        } else {
            mSmileyNotSelectedPreviously = false;
        }
        mSelectedSmile = smile;
        if (mFaceCenter == null) {
            return;
        }
        mValueAnimator.setFloatValues(mFaceCenter.x, point == null ? 0 : point.x);
        if (animate) {
            mValueAnimator.start();
        } else if (mSelectedSmile == NONE) {
            if (!mSmilePath.isEmpty()) {
                mSmilePath.reset();
            }
            invalidate();
        } else if (point != null) {
            moveSmile(point.x);
        }
    }

    private void createTouchPoints() {
        mTouchPoints.clear();
        float divisions = mWidth / 5f;
        float divCenter = divisions / 2f;
        mSmileGap = (divisions - mHeight) / 2f;
        mFromRange = mSmileGap + (mHeight / 2);
        mToRange = mWidth - (mHeight / 2) - mSmileGap;
        int count = SMILES_LIST.length;
        for (int i = 0; i < count; i++) {
            mFaces[i] = createFace(i, mCenterY);
            mTouchPoints.put(SMILES_LIST[i], new Point((divisions * i) + divCenter, mCenterY));
        }
    }

    private Face createFace(int index, float centerY) {
        Face face = new Face();
        face.smileType = index;
        getSmiley(mSmileys, index * 0.25f, divisions, mFromRange, mToRange, face.place, face.smile, centerY);
        face.place.y = centerY;
        return face;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        mSmileyNotSelectedPreviously = true;
        Point start = mFaces[0].place;
        Point end = mFaces[mFaces.length - 1].place;
        if (mShowLine) {
            canvas.drawLine(new ohos.agp.utils.Point(start.x, start.y), new ohos.agp.utils.Point(end.x, end.y),
                mPlaceholderLinePaint);
        }
        for (Face face : mFaces) {
            float scale = getScale(face.smileType);
            canvas.drawCircle(face.place.x, face.place.y, scale * (mHeight / 2), mPlaceHolderCirclePaint);
            mScaleMatrix.reset();
            face.smile.computeBounds(mScaleRect);
            if (mSmileyNotSelectedPreviously) {

                float nonSelectedScale = getScale(NONE);
                mScaleMatrix.setScale(nonSelectedScale, nonSelectedScale, mScaleRect.centerX(), mScaleRect.centerY());
                if (mSelectedSmile == face.smileType) {

                    scale = mFloatEvaluator.evaluate(1 - mMainSmileyTransformaFraction, 0, nonSelectedScale);
                }
            } else {

                mScaleMatrix.setScale(scale, scale, mScaleRect.centerX(), mScaleRect.centerY());
            }

            mDummyDrawPah.reset();
            mDummyDrawPah.addPath(face.smile, mScaleMatrix, Path.AddPathMode.APPEND_ADD_PATH_MODE);
            canvas.drawPath(mDummyDrawPah, mPlaceHolderFacePaint);
            float transY = 0.15f - (scale * 0.15f);
            mTextPaint.setColor(new Color(
                (Integer) mColorEvaluator.evaluate(((transY / 0.15f) - 0.2f) / 0.8f, mTextNonSelectedColor,
                    mTextSelectedColor)));
            drawTextCentered(getSmileName(face.smileType), face.place.x, face.place.y + (mHeight * (0.70f + transY)),
                mTextPaint, canvas);
        }
        if (!mSmilePath.isEmpty()) {
            if (mSmileyNotSelectedPreviously) {

                mPathPaint.setColor(new Color((Integer) mColorEvaluator.evaluate(mMainSmileyTransformaFraction,
                    mPlaceHolderFacePaint.getColor().getValue(), mDrawingColor)));

                mBackgroundPaint.setColor(new Color((Integer) mColorEvaluator.evaluate(mMainSmileyTransformaFraction,
                    mPlaceHolderCirclePaint.getColor().getValue(),
                    (mSelectedSmile == TERRIBLE) ? mAngryColor : mNormalColor)));

                mScaleMatrix.reset();
                mSmilePath.computeBounds(mScaleRect);
                float nonSelectedScale = 0.8f;
                mScaleMatrix.setScale(nonSelectedScale, nonSelectedScale, mScaleRect.centerX(), mScaleRect.centerY());
                mDummyDrawPah.reset();
                mDummyDrawPah.addPath(mSmilePath, mScaleMatrix, Path.AddPathMode.APPEND_ADD_PATH_MODE);

                canvas.drawCircle(mFaceCenter.x, mFaceCenter.y, nonSelectedScale * (mHeight / 2f), mBackgroundPaint);
                canvas.drawPath(mDummyDrawPah, mPathPaint);
            } else {
                canvas.drawCircle(mFaceCenter.x, mFaceCenter.y, mHeight / 2f, mBackgroundPaint);
                canvas.drawPath(mSmilePath, mPathPaint);
            }
        }
    }

    private void drawTextCentered(String text, float x, float y, Paint paint, Canvas canvas) {
        float xPos = x - (paint.measureText(text) / 2);
        float yPos = (y - ((paint.descent() + paint.ascent()) / 2));

        canvas.drawText(paint, text, xPos, yPos);
    }

    private void onClickView(float x, float y) {
        for (Integer smile : mTouchPoints.keySet()) {
            Point point = mTouchPoints.get(smile);
            boolean touched = isSmileyBounds(point.x, point.y, x, y, mCenterY);
            if (touched) {
                if (smile == getSelectedSmile()) {
                    notifyListener();
                } else {
                    setSelectedSmile(smile, point, true, true);
                }
            }
        }
    }

    private float getScale(@Smiley int smile) {
        if (mSelectedSmile == NONE) {
            return 0.80f;
        }
        if (smile == mNearestSmile) {
            return mPlaceHolderScale;
        }
        return 0.80f;
    }

    public String getSmileName(int smile) {
        if (smile >= mNames.length || smile < 0) {
            return null;
        }
        return mNames[smile];
    }

    private boolean isSmileyBounds(float cx, float cy, float tx, float ty, float width) {
        RectF src = new RectF(cx - width, 0, cx + width, getHeight());
        mTouchBounds.set(src);
        return mTouchBounds.isInclude(tx, ty);
    }

    @Smiley
    public int getSelectedSmile() {
        return mSelectedSmile;
    }

    public int getRating() {
        return getSelectedSmile() + 1;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (mIndicator) {

        }
        int action = touchEvent.getAction();
        int activePointerIndex = touchEvent.getIndex();
        MmiPoint point = touchEvent.getPointerPosition(activePointerIndex);
        float x = point.getX();
        float y = point.getY();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                mClickAnalyser.start(x, y);
                mFaceClickEngaged = isSmileyBounds(mFaceCenter.x, mFaceCenter.y, x, y, mCenterY);
                mPrevX = x;
                break;
            case TouchEvent.POINT_MOVE:
                mClickAnalyser.move(x, y);
                if (mClickAnalyser.isMoved() && mFaceClickEngaged) {
                    moveSmile(mFaceCenter.x - (mPrevX - x));
                }
                mPrevX = x;
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                mFaceClickEngaged = false;
                mClickAnalyser.stop(x, y);
                if (!mClickAnalyser.isMoved()) {
                    onClickView(x, y);
                } else {
                    positionSmile();
                }
                break;
        }
        return true;
    }

    private void positionSmile() {
        if (NONE == mSelectedSmile) {
            return;
        }
        float currentPosition = mFaceCenter.x;
        float distance = Integer.MAX_VALUE;
        Point point = null;
        @Smiley int smile = NONE;
        for (Integer s : mTouchPoints.keySet()) {
            Point p = mTouchPoints.get(s);
            float d = Math.abs(p.x - currentPosition);
            if (distance > d) {
                point = p;
                smile = s;
                distance = d;
            }
        }
        setSelectedSmile(smile, point, false, true);
    }

    @Override
    public boolean onEstimateSize(int i, int i1) {
        onMeasure();

        return false;
    }

    protected static class ClickAnalyser {
        private static final int MAX_CLICK_DISTANCE = 20;

        private static final int MAX_CLICK_DURATION = 200;

        private float mPressX;

        private float mPressY;

        private final float mDensity;

        private long mPressStartTime;

        private boolean mMoved = false;

        private boolean mClickEventOccured = true;

        public ClickAnalyser(float density) {
            mDensity = density;
        }

        public static ClickAnalyser newInstance(float density) {
            return new ClickAnalyser(density);
        }

        public void start(float x, float y) {
            mPressX = x;
            mPressY = y;
            mMoved = false;
            mClickEventOccured = true;
            mPressStartTime = System.currentTimeMillis();
        }

        /**
         * returns long press
         *
         * @param x
         * @param y
         * @return
         */
        public void move(float x, float y) {
            float dist = distance(mPressX, mPressY, x, y);

            long time = System.currentTimeMillis() - mPressStartTime;
            if (!mMoved && dist > MAX_CLICK_DISTANCE) {
                mMoved = true;
            }
            if ((time) > MAX_CLICK_DURATION || mMoved) {
                mClickEventOccured = false;
            }
        }

        public boolean stop(float x, float y) {
            move(x, y);
            return mClickEventOccured;
        }

        private float distance(float x1, float y1, float x2, float y2) {
            float dx = x1 - x2;
            float dy = y1 - y2;
            float distanceInPx = (float) Math.sqrt(dx * dx + dy * dy);
            return distanceInPx;

        }

        public boolean isMoved() {
            return mMoved;
        }

        private float pxToDp(float px) {
            return px / mDensity;
        }

    }

    private float getFractionBySmiley(int smiley) {
        switch (smiley) {

            case BaseRating.BAD:
                return 1f;
            case BaseRating.GOOD:
                return 0.75f;
            case BaseRating.GREAT:
                return 0.5f;
            case BaseRating.OKAY:
                return 0.25f;
            case BaseRating.TERRIBLE:
                return 0f;
        }
        return 0;
    }

    private void getSmiley(Smileys smileys, float fraction, float divisions, float fromRange, float toRange,
        Point point, Path smilePath, float centerY) {
        if (smileys == null) {
            return;
        }
        float actualTranslation = mFloatEvaluator.evaluate(fraction, fromRange, toRange);
        point.x = actualTranslation;
        float trans = actualTranslation - centerY;
        if (fraction > 0.75f) {
            fraction -= 0.75f;
            fraction *= 4;
            findNearestSmile(fraction, GOOD, GREAT);
            mBackgroundPaint.setColor(new Color(mNormalColor));
            transformSmile(trans, fraction, smilePath, smileys.getSmile(GOOD), smileys.getSmile(GREAT),
                mFloatEvaluator);
            createEyeLocation(smileys, divisions, fraction, actualTranslation, GREAT, smilePath, smilePath, centerY);
        } else if (fraction > 0.50f) {
            fraction -= 0.50f;
            fraction *= 4;
            findNearestSmile(fraction, OKAY, GOOD);
            mBackgroundPaint.setColor(new Color(mNormalColor));
            transformSmile(trans, fraction, smilePath, smileys.getSmile(OKAY), smileys.getSmile(GOOD), mFloatEvaluator);
            createEyeLocation(smileys, divisions, fraction, actualTranslation, GOOD, smilePath, smilePath, centerY);
        } else if (fraction > 0.25f) {
            fraction -= 0.25f;
            fraction *= 4;
            findNearestSmile(fraction, BAD, OKAY);
            mBackgroundPaint.setColor(new Color(mNormalColor));
            transformSmile(trans, fraction, smilePath, smileys.getSmile(BAD), smileys.getSmile(OKAY), mFloatEvaluator);
            createEyeLocation(smileys, divisions, fraction, actualTranslation, BAD, smilePath, smilePath, centerY);
        } else if (fraction >= 0) {
            fraction *= 4;
            findNearestSmile(fraction, TERRIBLE, BAD);
            mBackgroundPaint.setColor(
                new Color((Integer) mColorEvaluator.evaluate(fraction, mAngryColor, mNormalColor)));
            transformSmile(trans, fraction, smilePath, smileys.getSmile(TERRIBLE), smileys.getSmile(BAD),
                mFloatEvaluator);
            createEyeLocation(smileys, divisions, fraction, actualTranslation, TERRIBLE, smilePath, smilePath, centerY);
        } else {
            if (!mSmilePath.isEmpty()) {
                mSmilePath.reset();
            }
        }
    }

    private void findNearestSmile(float fraction, @Smiley int leftSmile, @Smiley int rightSmile) {
        if (fraction < 0.5f) {
            mPlaceHolderScale = limitNumberInRange(fraction * 2);
            mNearestSmile = leftSmile;
        } else {
            mPlaceHolderScale = limitNumberInRange(1f - (fraction - 0.5f) * 2);
            mNearestSmile = rightSmile;
        }
    }

    private float limitNumberInRange(float num) {
        // The range is going to be in between 0 to 0.80
        num *= 0.80f;
        return num;
    }

    private void createEyeLocation(Smileys smileys, float divisions, float fraction, float actualTranslation,
        @Smiley int smile, Path leftEye, Path rightEye, float centerY) {
        Eye eyeLeft = EyeEmotion.prepareEye(smileys.getEye(Eye.LEFT), mFloatEvaluator, fraction, smile);
        Eye eyeRight = EyeEmotion.prepareEye(smileys.getEye(Eye.RIGHT), mFloatEvaluator, fraction, smile);
        eyeLeft.radius = divisions * 2.5f;
        eyeRight.radius = divisions * 2.5f;
        eyeLeft.center.x = (divisions * 11f) + actualTranslation - centerY;
        eyeLeft.center.y = centerY * 0.70f;
        eyeRight.center.x = (divisions * 21f) + actualTranslation - centerY;
        eyeRight.center.y = centerY * 0.70f;
        eyeLeft.fillPath(leftEye);
        eyeRight.fillPath(rightEye);
    }

    private void moveSmile(float position) {
        float fraction = (position - mFromRange) / (mToRange - mFromRange);
        moveSmileByFraction(fraction);
    }

    private void moveSmileByFraction(float fraction) {
        fraction = Math.max(Math.min(fraction, 1.0f), 0.0f);
        getSmiley(mSmileys, fraction, divisions, mFromRange, mToRange, mFaceCenter, mSmilePath, mCenterY);
        invalidate();
    }

    private static class Face {

        Point place = new Point();

        Path smile = new Path();

        @Smiley
        int smileType;
        /*Path leftEye = new Path();
        Path rightEye = new Path();*/
    }

    public interface OnSmileySelectionListener {
        /**
         * Called when a smiley is selected
         *
         * @param smiley is the type of smiley the user selected ({@link #GREAT}, {@link #BAD},
         * {@link #OKAY},{@link #GOOD},{@link #GREAT})
         * @param reselected is false when user selects different smiley that previously selected
         * one true when the same smiley is selected. Except if it first time,
         * then the value will be false.
         */
        void onSmileySelected(@Smiley int smiley, boolean reselected);
    }

    public interface OnRatingSelectedListener {
        /**
         * Called when a smiley is selected
         *
         * @param level is the level of the rating (0 to 4)
         * @param reselected is false when user selects different smiley that previously selected
         * one true when the same smiley is selected. Except if it first time,
         * then the value will be false.
         */
        void onRatingSelected(int level, boolean reselected);
    }
}
